<script setup>
import {onMounted, reactive, ref, inject, provide, watch} from "vue";
import 'jiuqi-ui'
import NCell, {BooleanCell, buildCellSheet,buildForm, TreeCell} from 'n-cell'
import GenerateFormulaEnterance from "./formula/generateFormulaEnterance.vue";
import {Upload} from "view-ui-plus";
import {useMessage} from "jiuqi-ui";
import NCELLCSS from "../../utils/NCELLCSS";
import {getTreeNodeByGuid} from "../../utils/TreeHelper"

//================================================【组件通信】============================================================
//控制 取数公式入口模态框 显示与隐藏
const genFormulaVisible = ref(false)
provide('genFormulaVisible',genFormulaVisible)

// 请求工具
const context = inject('context')

//给父组件提供最新的 是否需要保存 情况
const tabDataChanged = inject('tabDataChanged')

// 当前模型ID
const {currAbcModelGuid} = inject('abcmodel')

//宏显式暴露
defineExpose({
   save,refreshNcellData,checkTabContentChanged
})

//==================================================================【获取子组件 传来的参数】
function processFormula(params) {

    //批量生成公式
    for (let i = 0; i < selectedPoolItemArr.length; i++) {
        const selectedCellItemRow = selectedPoolItemArr[i]
        const selectedCellItemCode = ncell.getData(selectedCellItemRow, 1)
        let formula = params.tableName + '[' + params.quotaName + ',' + params.memberCode + '="' + selectedCellItemCode + '",sum]'
        ncell.setData(selectedCellItemRow, 3, formula)


        //已选中的 选择框  还原成  非选中状态
        //不调用 ncell api，直接编辑值  不会触发 celldatachange事件
        ncell.getSheet().data[selectedCellItemRow][0].v = false
    }

    //维护 已选择成本池维度项数组,删除 已经生成取数公式的成本池维度项
    selectedPoolItemArr.splice(0,selectedPoolItemArr.length)


    //取消第一列"全选选择框"的选中状态
    ncell.getSheet().data[0][0].v = false

    //设置保存按钮状态[可用]
    saveDisable.value = false

}
//================================================【公用相关】============================================================
// 控制 保存按钮 是否可用
const saveDisable= ref(true)

//控制 导出按钮 是否可用
const exportDisable = ref(false)

//控制 生成公式按钮 是否可用
const generateFormulaDisable = ref(true)
//消息组件
const message = useMessage()
//不断增加的行号.非表头的第一行
let allItemCount = ref(1)
//已选择成本池维度项数组
const selectedPoolItemArr = reactive([])
//==================================================================【递归设置数据】
function recursiveSetData(arr) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i].subMemberResourceList === null) {
            //将叶子节点维度的值赋给单元格
            ncell.setCellType(allItemCount.value, 0, 'boolean')
            ncell.setData(allItemCount.value, 1, arr[i].code)
            ncell.setData(allItemCount.value, 2, arr[i].name)
            ncell.setData(allItemCount.value, 3,arr[i].formula === null ? '' : arr[i].formula)
            //设置缩进
            ncell.setIndent(allItemCount.value, 1, arr[i].tableLevel + 2.1)
            ncell.setIndent(allItemCount.value, 2, arr[i].tableLevel + 0.7)
            ncell.setIndent(allItemCount.value, 3, 0.7)
            //行数加一
            allItemCount.value++
        } else {
            //将父节点维度的值赋给单元格
            ncell.setCellType(allItemCount.value, 0, 'boolean')
            ncell.setData(allItemCount.value, 1, arr[i].code)
            ncell.setData(allItemCount.value, 2, arr[i].name)
            ncell.setData(allItemCount.value, 3, arr[i].formula === null ? '' : arr[i].formula)
            //设置缩进
            ncell.setIndent(allItemCount.value, 1, arr[i].tableLevel + 0.7)
            ncell.setIndent(allItemCount.value, 2, arr[i].tableLevel + 0.7)
            ncell.setIndent(allItemCount.value, 3, 0.7)
            //设置单元格为树形单元格
            ncell.setCellType(allItemCount.value, 1, 'tree')
            //行数加一
            allItemCount.value++
            //深度递归遍历
            recursiveSetData(arr[i].subMemberResourceList)
        }
    }
}

//设置内容只读
function setContentReadOnly() {
    let columnCount = ncell.getColumnCount()
    for (let i = 0; i < rowCount.value; i++) {
        for (let j = 1; j < columnCount - 1; j++) {
            ncell.setCellReadOnly(i,j)
        }
    }
}

//设置内容可写
function setContentWritable() {
    let columnCount = ncell.getColumnCount()
    for (let i = 0; i < rowCount.value; i++) {
        for (let j = 1; j < columnCount -1; j++) {
            ncell.setCellWritable(i,j)
        }
    }
}

//================================================【组件属性】============================================================
watch(()=>saveDisable.value,(newValue)=>{
    exportDisable.value = !newValue

    //给父组件提供最新的 是否需要保存 情况
    tabDataChanged(!newValue,'save','refreshNcellData')

},{deep: true})
watch(selectedPoolItemArr,(newValue)=>{
    generateFormulaDisable.value = newValue.length === 0;
})
//================================================【普通方法】============================================================
//==================================================================【设置ncell容器高度】
let ncellContainerHeight = ref(0)
function setNcellContainerStyle() {
    ncellContainerHeight.value = document.body.clientHeight - 128
}
//==================================================================【获取成本池数据】
//页面数据数组
let dimArray = reactive([])

//ncell 展示行数，需要从后端获取
const rowCount = ref(0)
// 构建表样
let cellSheet = reactive(null)
let data = reactive(null)

function getRequestData() {
    context.getPlugin('http').get(`abc/model/resourceMember/list/${currAbcModelGuid.value}`)
        .then((res) => {
            // 将 res.data 直接赋值给 dimArray  插入元素的索引位置，删除的个数，添加的元素
            dimArray.splice(0, dimArray.length, ...res.data.showList)

            //设置ncell展示行数
            rowCount.value = res.data.totalCount

            //构建表样
            cellSheet = buildForm(rowCount.value + 1, 4,'cellSheet')
            data= buildCellSheet(cellSheet)

            //初始化ncell
            initNewCell();

            // 设置数据（注意异步情况，如果该方法放到了then外，有可能因为数组为空不显示渲染效果）
            recursiveSetData(dimArray);

            //设置内容只读
            setContentReadOnly()

            //初始化渲染数据后，因为触发监听事件，保存按钮状态变为可用，需要还原为不可用
            saveDisable.value = true


        })
}

//==================================================================【设置新报表组件】
// 用ref拿到div节点,这一步不能放到initNewCell()中否则程序会报错，应该和vue生命周期有关
const ncell_costPool = ref(null)
//ncell对象
let ncell = reactive(null)
function initNewCell() {

    ncell = new NCell(
        ncell_costPool.value,
        data,
        {
            cell: [
                // 引入判断单元格
                BooleanCell,

                // 引入树形单元格
                TreeCell
            ]
        },
        {forceShowGridline: true,resizeMode:'all'}
    )
    //设置表格样式
    setNcellStyle();
    //设置监听事件
    setNcellListenEvent();
}
//==================================================================【设置ncell样式】
function setNcellStyle() {
    //表头设置
    for (let i = 0; i < ncell.getColumnCount(); i++) {
        //字体大小
        ncell.setFontSize(0, i, NCELLCSS.fontSize)
        //字体加粗
        ncell.setBold(0, i, NCELLCSS.headerBold)
        //字体颜色
        ncell.setFontColor(0, i, NCELLCSS.fontColor)

        //对齐方式
        ncell.setHorizontal(0, i, 1)
        //缩进
        ncell.setIndent(0,i,0.5)
        //背景颜色
        ncell.setBackgroundColor(0,i,'#f2f2f2')

        //设置高度
        ncell.setRowSize(0, 30)
        //设置宽度
        switch (i) {
            case 0:
                ncell.setColumnSize(i, 32)
                ncell.setCellType(0, i, 'boolean')
                break;
            case 1:
                ncell.setColumnSize(i, 298)
                ncell.setData(0,i,'成本池编码')
                break;
            case 2:
                ncell.setColumnSize(i, 298)
                ncell.setData(0,i,'成本池名称')
                break;
            case 3:
                ncell.setColumnSize(i, 730)
                ncell.setData(0,i,'取数公式')
        }
    }

    //非表头行设置
    for (let i = 1; i < ncell.getRowCount(); i++) {
        ncell.setRowSize(i,30)
        for (let j = 0; j < ncell.getColumnCount(); j++){
            //对齐方式
            ncell.setHorizontal(i, j, 1)
            //缩进
            ncell.setIndent(i,j,0.5)

            //公式单元格 设置 等宽字体族
            if (j === 3)
              ncell.setFontName(i,j,'Courier New')
        }
    }

    //隐藏行列号
    ncell.hiddenSerialNumberHeader()

    //处理因为隐藏行列号，导致边界消失情况
    ncell.setOffsetX(1)
    ncell.setOffsetY(1)
}
//==================================================================【设置监听事件】
function setNcellListenEvent() {

    // 内置方法，折叠时触发
    ncell.on('rowfold', async (rowIndex) => {
        //获取 点击按钮的行 对应的 树形节点数据
        const treeNode = getTreeNodeByGuid(dimArray, ncell.getData(rowIndex, 1))
        //获取 该节点下属节点 对应的 结束行
        const endIndex = rowIndex + treeNode.subDimItemCount
        for (rowIndex = rowIndex + 1; rowIndex <= endIndex; rowIndex++) {
            ncell.hiddenRow(rowIndex)
        }
    })

    // 内置方法，展开时触发
    ncell.on('rowunfold', async (rowIndex) => {
        //获取 点击按钮的行 对应的 树形节点数据
        const treeNode = getTreeNodeByGuid(dimArray, ncell.getData(rowIndex, 1))
        //获取 该节点下属节点 对应的 结束行
        const endIndex = rowIndex + treeNode.subDimItemCount
        for (rowIndex = rowIndex + 1; rowIndex <= endIndex; rowIndex++) {
            //是折叠行
            if (ncell.isRowFold(rowIndex)) {
                //折叠行本身应该显示
                ncell.showRow(rowIndex)
                //获取 折叠行 对应节点 的下属节点;下属节点 对应的行 应该隐藏
                const treeNode = getTreeNodeByGuid(dimArray, ncell.getData(rowIndex, 1))
                const subNodeCount = treeNode.subDimItemCount
                //跳过处理，继续保持隐藏状态
                rowIndex = rowIndex + subNodeCount;

            //不是折叠行
            }else {
                ncell.showRow(rowIndex)
            }
        }
    })


    //添加ncell监听事件：
    // 当 手动编辑 公式时，编辑保存按钮状态[可用]
    // 当 选择 checkbox 时，维护 已选择成本池维度项数组
    ncell.on('celldatachange',(rowIndex,columnIndex)=>{
        if (columnIndex === 3){
            //设置编辑按钮状态[可用]
            ncell.setFontName(rowIndex,columnIndex,'Courier New')
            saveDisable.value = false
        }
        if (columnIndex === 0) {

            if (rowIndex === 0) {
                let checkAll = ncell.getData(0,0)
                if (checkAll) {
                    for (let i = 1; i < rowCount.value + 1; i++) {
                        //不调用 ncell api，直接编辑值  不会触发 celldatachange事件
                        ncell.getSheet().data[i][0].v = true
                    }
                }else {
                    for (let i = 1; i < rowCount.value + 1; i++) {
                        ncell.getSheet().data[i][0].v = false
                    }
                }
            }

            //获取当前选择的成本池维度项列表
            selectedPoolItemArr.splice(0,selectedPoolItemArr.length)
            for (let i = 1; i < rowCount.value + 1; i++) {
                const cellData = ncell.getData(i,0);
                if (cellData === true)
                    selectedPoolItemArr.push(i)
            }
        }
    })

    ncell.on('mousein',(res)=>{
        if (res.entity.r > 0){
            for (let i = 0; i < ncell.getColumnCount(); i++) {
                ncell.setBackgroundColor(res.entity.r,i,'#E4F0FF')
            }
        }
    })

    ncell.on('mouseout',(res)=>{
        if (res.entity.r > 0){
            for (let i = 0; i < ncell.getColumnCount(); i++) {
                ncell.setBackgroundColor(res.entity.r,i,'#ffffff00')
            }
        }
    })
}

//==================================================================【保存】
const tableValue = reactive([])
//保存按钮加载状态
const saveLoading = ref(false)
function save(){
    return new Promise((resolve, reject)=>{
        if (saveLoading.value) {
            reject()
            return
        }
        //开始加载状态
        saveLoading.value = true
        //避免数据已经输入，因未失焦，导致数据不能保存
        ncell.closeEditor();
        //数组初始化
        tableValue.splice(0,tableValue.length)
        for (let i = 1; i < ncell.getRowCount(); i++) {
            const lineValue = reactive({})
            let formulaValue = ncell.getData(i,3);
            // 只保存 成本池取数公式  有值的项
            if (formulaValue !== null && formulaValue !== '') {
                lineValue.memberCode = ncell.getData(i,1)
                lineValue.formula = formulaValue
                tableValue.push(lineValue)
            }
        }

        context.getPlugin('http').post(`abc/model/resourceMember/save/${currAbcModelGuid.value}`,tableValue).then(() => {
            message.success('保存成功')
            saveDisable.value = true
            //结束加载状态
            saveLoading.value = false
            resolve('保存成功')
        }).catch(() => {
            message.error('保存失败')
            //结束加载状态
            saveLoading.value = false
            reject('保存失败')
        })
    })
}

//==================================================================【生成取数公式】
function generateFormula() {
    //打开取数公式入口模态框，开始生成公式
    genFormulaVisible.value = true;
}
//==================================================================【成本池导入】
let file = new reactive(null)
//保存按钮加载状态
const handleUploadLoading = ref(false)
function handleUpload(uploadFile) {
    if (handleUploadLoading.value) {
        return
    }
    //开始加载状态
    handleUploadLoading.value = true
    file = uploadFile;
    //阻止默认事件，使用context上传文件
    context.getPlugin('http').post(`abc/model/resourceMember/import/${currAbcModelGuid.value}`, {'file': file}, {
        headers: {
            'Content-Type': 'multipart/form-data',
            'x-requested-with': "XMLHttpRequest",
            // eslint-disable-next-line no-undef
            'Authorization': context.getPlugin('http').getConfig().headers.Authorization
        }
    }).then((res) =>{
        if (res.data.status === '成功') {
            refreshNcellData()
        }else {
            message.error(res.data.cause)
        }
        //结束加载状态
        handleUploadLoading.value = false
    }).catch(() => {
        message.error('导入失败')
        //结束加载状态
        handleUploadLoading.value = false
    })

    //阻止默认上传事件
    return false
}
//==================================================================【刷新ncell数据】
function refreshNcellData(){

    //从数据库 中 获取刚才上传的文件
    context.getPlugin('http').get(`abc/model/resourceMember/list/${currAbcModelGuid.value}`)
        .then((res) => {

            //将旧数据删除
            ncell.deleteRows(0,rowCount.value+1)

            // 将 res.data 直接赋值给 dimArray  插入元素的索引位置，删除的个数，添加的元素
            dimArray.splice(0, dimArray.length, ...res.data.showList)

            //设置ncell展示行数
            rowCount.value = res.data.totalCount

            //添加新数据
            ncell.insertRows(0, rowCount.value);

            //设置表格样式
            setNcellStyle()

            //设置内容可写
            setContentWritable()

            // 设置数据
            // 不断增加的 行号 需要 重置 为0
            allItemCount.value = 1
            recursiveSetData(dimArray);
            //设置内容只读
            setContentReadOnly()

            //成本池导入采取直接入库，后从数据库中查询最新数据的方式，无需保存。将因触发监听事件而改变的保存按钮状态 进行还原。
            saveDisable.value = true


        });

}
//==================================================================【关闭ncell编辑状态】
function checkTabContentChanged() {
    //手动关闭编辑状态
    ncell.closeEditor();

    //提供最新的 是否需要保存 情况
    return !saveDisable.value
}
//==================================================================【成本池导出】
//导出按钮加载状态
const costPoolOutputLoading = ref(false)
function costPoolOutput() {
    if (costPoolOutputLoading.value) {
        return
    }
    //开始加载状态
    costPoolOutputLoading.value = true
    context.getPlugin('http').post(`abc/model/resourceMember/export/${currAbcModelGuid.value}`,{}, {
        headers: {
            // 'Content-Type': 'application/octet-stream',
            'x-requested-with': "XMLHttpRequest",
            // eslint-disable-next-line no-undef
            'Authorization': context.getPlugin('http').getConfig().headers.Authorization
        },
        responseType: 'blob'
    }).then(res => {
        const blob = new Blob([res.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
        const url = window.URL.createObjectURL(blob)
        const aLink = document.createElement('a')
        aLink.style.display = 'none'
        aLink.href = url

        const exportTitle = '成本池.xlsx'
        aLink.setAttribute('download',exportTitle)
        document.body.appendChild(aLink)
        aLink.click()
        document.body.removeChild(aLink) // 下载完成移除元素
        window.URL.revokeObjectURL(url) // 释放掉blob对象

        //结束加载状态
        costPoolOutputLoading.value = false
    }).catch(() => {
        message.error('导入失败')
        //结束加载状态
        costPoolOutputLoading.value = false
    });
}
//================================================【生命周期】============================================================
//mounted 初始化页面完成进行操作
onMounted(async () => {
    //当浏览器页面大小改变，ncell表格高度对应更新
    window.addEventListener('resize', function () {
        ncellContainerHeight.value = document.body.clientHeight - 128
    })
    setNcellContainerStyle()
    await getRequestData();
});
</script>

<template>
        <j-layout class="layout">

            <!--<editor-fold desc="头部菜单栏">-->
            <j-header class="toolbar">
                    <j-space :size="16">
                        <j-button @click="save" :disabled="saveDisable" :loading="saveLoading">保存</j-button>
                        <j-button @click="generateFormula" :disabled="generateFormulaDisable" >生成取数公式</j-button>
                        <Upload :before-upload="handleUpload" :disabled="handleUploadLoading">
                            <j-button :loading="handleUploadLoading">导入</j-button>
                        </Upload>
                        <j-button @click="costPoolOutput" :disabled="exportDisable" :loading="costPoolOutputLoading">导出</j-button>
                    </j-space>
            </j-header>
            <!--</editor-fold>-->

            <!--<editor-fold desc="页面主体内容">-->
            <j-content>
                <div ref="ncell_costPool" style="width: 100%" :style="{height:ncellContainerHeight+'px'}"/>
            </j-content>
            <!--</editor-fold>-->

        </j-layout>

    <GenerateFormulaEnterance
        :show="genFormulaVisible"
        @selfConfirmReturnParam="processFormula"
    ></GenerateFormulaEnterance>
</template>

<style scoped>
.toolbar {
    position: relative;
    height: 50px !important;
    line-height: 50px;
}
/*导入按钮格式*/
.ivu-upload{
    display: flex !important;
    align-items: center !important;
}
:deep(.ivu-upload-select){
    display: flex !important;
}
</style>
